﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Input.Manipulations;
using Microsoft.Surface.Presentation.Input;
using ZOIL.Framework.Core;
using ZOIL.Framework.Presentation;
//using Microsoft.Surface.Presentation;
//using Microsoft.Surface.Presentation.Manipulations;

namespace ZOIL.Library.Behaviors
{
    [Obsolete("Support for Microsoft Surface 1 discontinued", false)]
    public static class ZObjectSurfaceHandler
    {
        private static readonly Dictionary<ZComponent, ZObjectManipulationInformation> ElementsBeingManipulated =
            new Dictionary<ZComponent, ZObjectManipulationInformation>();

        private static readonly Dictionary<InertiaProcessor2D, ZObjectManipulationInformation> ElementsBeingAccelerated =
            new Dictionary<InertiaProcessor2D, ZObjectManipulationInformation>();

        private static readonly Dictionary<FrameworkElement, TouchDevice> _contactsDown = new Dictionary<FrameworkElement, TouchDevice>();

        #region Eventhandler

        public static void OnContactTapGesture(object sender, TouchEventArgs e)
        {
            if (!e.TouchDevice.GetIsFingerRecognized())
            {
                return;
            }

            //possible Multiple Click Zoom
            if (_contactsDown.Count > 1)
            {
                return;
            }

            FrameworkElement zoomTarget = ZTreeHelper.GetClosestZoomTarget(sender as FrameworkElement, e.Source as FrameworkElement);
            if (zoomTarget != null)
            {
                var landscape = ZTreeHelper.TryFindParent<ZInformationLandscape>(sender as DependencyObject);
                if (landscape != null)
                {
                    landscape.ZoomOnto(zoomTarget, false, true);
                    zoomTarget.RaiseEvent(new RoutedEventArgs(ZInformationLandscape.ClickZoomEvent, zoomTarget));
                }
            }
            e.Handled = true;
        }

        public static void OnContactDown(object sender, TouchEventArgs e)
        {
            if (!e.TouchDevice.GetIsFingerRecognized())
            {
                return;
            }

            var comp = sender as ZComponent;

            if (comp != null)
            {
                Panel.SetZIndex(comp, ZComponent.ZIndexCounter++);

                if (comp.InertiaProcessor != null && comp.InertiaProcessor.IsRunning)
                {
                    try
                    {
                        Manipulation.CompleteManipulation(comp);
                        //comp.InertiaProcessor.End();
                    }
                    catch (Exception ex)
                    {
                        //do nothing
                    }
                }

                if (!ElementsBeingManipulated.ContainsKey(comp))
                {
                    var info = new ZObjectManipulationInformation(comp);
                    info.ElementBeingDragged = ZBehavior.FindCanvasChild(sender as UIElement);
                    info.OriginalPositionOnWindow = e.TouchDevice.GetPosition(null);

                    info.OriginalFrameworkElement = ((e.Source as FrameworkElement != null)
                                                         ? e.Source as FrameworkElement
                                                         : info.ElementBeingDragged as FrameworkElement);

                    // TODO: replace using TransformToAncestor()
                    info.ParentLandscapes = ZTreeHelper.TryFindParents<ZInformationLandscape>(comp);
                    info.Left = Canvas.GetLeft(comp);
                    info.Top = Canvas.GetTop(comp);
                    info.Touches.Add(e.TouchDevice);

                    info.OriginalPositionOnCanvas = new Point(info.Left, info.Top);

                    ElementsBeingManipulated[comp] = info;
                    comp.ManipulationProcessor = null;
                    InitProcessor(comp, ZObjectDragDropBehavior.GetIsDraggable(comp), ZObjectResizeBehavior.GetIsResizable(comp),
                                  ZObjectRotateBehavior.GetIsRotatable(comp));

                    //comp.ManipulationProcessor.Affine2DManipulationDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(ManipulationProcessor_Affine2DManipulationDelta);
                    //comp.ManipulationProcessor.Affine2DManipulationCompleted += new EventHandler<Affine2DOperationCompletedEventArgs>(ManipulationProcessor_Affine2DManipulationCompleted);

                    comp.ManipulationProcessor.Delta += ManipulationProcessor_Manipulation2DDelta;
                    comp.ManipulationProcessor.Completed += ManipulationProcessor_Manipulation2DCompleted;
                }
                else
                {
                    ZObjectManipulationInformation info = ElementsBeingManipulated[comp];
                    info.Touches.Add(e.TouchDevice);
                }

                Manipulation.AddManipulator(comp, e.TouchDevice);
                e.TouchDevice.Capture(comp);
            }
            e.Handled = true;
        }

        public static void OnContactHold(object sender, TouchEventArgs e)
        {
            if (!e.TouchDevice.GetIsFingerRecognized())
            {
                return;
            }

            var fe = sender as FrameworkElement;
            //if fe is a framework element, is not in the list yet and is child of a landscape, then add...
            if (fe != null && !_contactsDown.Keys.Contains(fe) && ZTreeHelper.TryFindParent<ZInformationLandscape>(fe) != null)
            {
                Panel.SetZIndex(fe, ZComponent.ZIndexCounter++);
                _contactsDown.Add(fe, e.TouchDevice);
            }
        }

        public static void OnContactUp(object sender, TouchEventArgs e)
        {
            if (_contactsDown.Count >= 2 && _contactsDown.Values.Contains(e.TouchDevice))
            {
                var list = new List<FrameworkElement>();
                foreach (FrameworkElement fe in _contactsDown.Keys)
                {
                    list.Add(fe);
                }

                //list.Add(_contactsDown.Keys.ToArray()[1]);
                var mcz = new MultipleClickZoomBehavior(ZTreeHelper.TryFindParent<ZInformationLandscape>(sender as DependencyObject));
                mcz.RaiseMultipleClickEvent(sender as FrameworkElement, list);
                _contactsDown.Clear();
            }

            if (_contactsDown.Keys.Contains(sender as FrameworkElement) && (e.TouchDevice == _contactsDown[sender as FrameworkElement]))
            {
                _contactsDown.Remove(sender as FrameworkElement);
            }
        }

        #endregion

        private static void InitProcessor(ZComponent comp, bool supportsDragging, bool supportsResizing, bool supportsRotating)
        {
            var manipulations = Manipulations2D.None;

            if (supportsDragging)
            {
                manipulations |= Manipulations2D.TranslateX;
                manipulations |= Manipulations2D.TranslateY;
            }
            if (supportsResizing)
            {
                manipulations |= Manipulations2D.Scale;
            }
            if (supportsRotating)
            {
                manipulations |= Manipulations2D.Rotate;
            }

            if (comp.ManipulationProcessor == null)
            {
                //comp.ManipulationProcessor = new Affine2DManipulationProcessor(manipulations, comp, true);
                comp.ManipulationProcessor = new ManipulationProcessor2D(manipulations);
            }
            else
            {
                comp.ManipulationProcessor.SupportedManipulations = manipulations;
            }
        }

        #region manipulation

        private static void ManipulationProcessor_Manipulation2DDelta(object sender, Manipulation2DDeltaEventArgs e)
        {
            //Affine2DManipulationProcessor processor = sender as Affine2DManipulationProcessor;
            var processor = sender as ManipulationProcessor2D;

            //ZComponent comp = processor.Container as ZComponent;
            var comp = sender as ZComponent;

            if (comp != null)
            {
                if (ElementsBeingManipulated.ContainsKey(comp))
                {
                    ZObjectManipulationInformation info = ElementsBeingManipulated[comp];

                    if (comp != info.ComponentBeingManipulated)
                    {
#if DEBUG
                        ZDebug.Log(DebugLevel.Warning, DebugCategories.Components, "Warning, treating a component in two different ways!");
#endif
                    }

                    // TODO: dragging works well with following code, without own dragging method --> 
                    // evaluate flag for dragging true/false somewhere below
                    if (ZObjectResizeBehavior.GetIsResizable(comp) || ZObjectDragDropBehavior.GetIsDraggable(comp))
                    {
                        #region Fire Drag Events, TODO: extract this to extra method

                        //if (ZObjectBlubAndBinDragDropBehavior.GetIsDraggable(comp) && e.ScaleDelta == 1 && e.CumulativeTranslation.Length > 10)
                        if (ZObjectDragDropBehavior.GetIsDraggable(comp) && e.Delta.ScaleX == 1 && e.Delta.ScaleY == 1 &&
                            e.Cumulative.TranslationX > 10 && e.Cumulative.TranslationY > 10)
                        {
                            //if we are not dragging yet fire drag started event and set is draggiing to true
                            if (!ZObjectDragDropBehavior.GetIsDragging(comp))
                            {
                                var args = new ZObjectDragDropEventArgs();
                                args.RoutedEvent = ZObjectDragDropBehavior.DragStartedEvent;
                                args.Source = comp;
                                args.CurrentDragPoint = new Point(e.OriginX, e.OriginY);
                                comp.RaiseEvent(args);
                                ZObjectDragDropBehavior.SetIsDragging(comp, true);
                            }
                                //otherwise fire only drag delta event
                            else
                            {
                                if (info.DeltaFired)
                                {
                                    info.DeltaFired = false;
                                }
                                else
                                {
                                    info.DeltaFired = true;
                                    //Raise DragDeltaEvent
                                    var args = new ZObjectDragDropEventArgs();
                                    args.CurrentDragPoint = new Point(e.OriginX, e.OriginY);
                                    args.RoutedEvent = ZObjectDragDropBehavior.DragDeltaEvent;
                                    args.Source = comp;
                                    comp.RaiseEvent(args);
                                }
                            }
                        }
                        else
                        {
                            ZObjectDragDropBehavior.SetIsDragging(comp, false);
                        }

                        #endregion

                        var Center = new Point(Canvas.GetLeft(comp) + comp.Width/2, Canvas.GetTop(comp) + comp.Height/2);

                        #region Visual Debug

                        // YELLOW: Center -> visual point for debugging
                        //Ellipse cen = new Ellipse();
                        //cen.Width = 10;
                        //cen.Height = 10;
                        //cen.Fill = new SolidColorBrush(Colors.Yellow);
                        //((Canvas)comp.ParentInformationLandscape.Content).Children.Add(cen);
                        //Canvas.SetLeft(cen, Center.X);
                        //Canvas.SetTop(cen, Center.Y);

                        #endregion

                        // TODO: add nested landscape support
                        double ILScale = info.GetScaleFactor();
                        //Vector deltaXY = e.Delta / ILScale;
                        var deltaXY = new Vector(e.Delta.ScaleX/ILScale, e.Delta.ScaleY/ILScale);
                        // gets manipulation origin in screen(!) coordinates
                        var lastManipulationOrigin = new Point(e.OriginX, e.OriginY);
                        // convert screen coordinates to window coordinates
                        lastManipulationOrigin = Window.GetWindow(comp).PointFromScreen(lastManipulationOrigin);
                        // convert window coordinates to landscape coordinates
                        lastManipulationOrigin = comp.ParentInformationLandscape.TransformWindowToLandscapeCoordinates(lastManipulationOrigin);
                        lastManipulationOrigin -= deltaXY;

                        #region VisualDebug

                        // RED: LastManipulationOrigin -> visual point for debugging
                        //Ellipse lmo = new Ellipse();
                        //lmo.Width = 10;
                        //lmo.Height = 10;
                        //lmo.Fill = new SolidColorBrush(Colors.Red);                          
                        //((Canvas)comp.ParentInformationLandscape.Content).Children.Add(lmo);
                        //Canvas.SetLeft(lmo, lastManipulationOrigin.X);
                        //Canvas.SetTop(lmo, lastManipulationOrigin.Y);

                        #endregion

                        Vector vector = (Center - lastManipulationOrigin);

                        double scaleX = 1.0;
                        double scaleY = 1.0;
                        if (ZObjectResizeBehavior.GetIsResizable(comp))
                        {
                            scaleX = e.Delta.ScaleX;
                            scaleY = e.Delta.ScaleY;
                        }

                        Vector vector4 = (vector*scaleX);
                        deltaXY += vector4 - vector;

                        //components size is adjusted to new scale
                        comp.Width *= scaleX;
                        comp.Height *= scaleY;

                        //look for a translation during resizing and adjust the component position
                        if (deltaXY.LengthSquared > 0.0)
                        {
                            Point center = Center;
                            center.X += deltaXY.X;
                            center.Y += deltaXY.Y;
                            Center = center;

                            Canvas.SetLeft(comp, Center.X - comp.Width/2.0);
                            Canvas.SetTop(comp, Center.Y - comp.Height/2.0);
                        }

                        #region Visual Debug

                        // GREEN: new center -> visual point for debugging
                        //Ellipse newcen = new Ellipse();
                        //newcen.Width = 10;
                        //newcen.Height = 10;
                        //newcen.Fill = new SolidColorBrush(Colors.Green);
                        //((Canvas)comp.ParentInformationLandscape.Content).Children.Add(newcen);
                        //Canvas.SetLeft(newcen, Center.X);
                        //Canvas.SetTop(newcen, Center.Y);

                        #endregion
                    }

                    //if component is rotated adjust the angle value
                    if (ZObjectRotateBehavior.GetIsRotatable(comp))
                    {
                        comp.Angle += e.Delta.Rotation;
                    }

                    /* //TODO: for flexibility, separate drag and resize, currently this code is inside resizing
                    if (ZObjectDragDropBehavior.GetIsDraggable(comp) && e.ScaleDelta == 1)
                    {
                        //if we are not dragging yet fire drag started event and set is draggiing to true
                        if (!ZObjectDragDropBehavior.GetIsDragging(comp))
                        {
                            ZObjectDragDropEventArgs args = new ZObjectDragDropEventArgs();
                            args.RoutedEvent = ZObjectDragDropBehavior.DragStartedEvent;
                            args.Source = comp;
                            args.CurrentDragPoint = e.ManipulationOrigin;
                            comp.RaiseEvent(args);
                            ZObjectDragDropBehavior.SetIsDragging(comp, true);
                        }
                        //otherwise fire only drag delta event
                        else
                        {
                            //Raise DragDeltaEvent
                            ZObjectDragDropEventArgs args = new ZObjectDragDropEventArgs();
                            args.CurrentDragPoint = e.ManipulationOrigin;
                            args.RoutedEvent = ZObjectDragDropBehavior.DragDeltaEvent;
                            args.Source = comp;
                            comp.RaiseEvent(args);
                        }

                        double scale = info.getScaleFactor();

                        Canvas.SetLeft(comp, info.Left + e.CumulativeTranslation.X / scale);
                        Canvas.SetTop(comp, info.Top + e.CumulativeTranslation.Y / scale);
                    }
                    */
                }
            }
        }

        private static void ManipulationProcessor_Manipulation2DCompleted(object sender, Manipulation2DCompletedEventArgs e)
        {
            var processor = sender as ManipulationProcessor2D;
            //ZComponent comp = processor.Container as ZComponent;
            var comp = Manipulation.GetManipulationContainer(sender as UIElement) as ZComponent;
            if (comp != null)
            {
                if (ElementsBeingManipulated.ContainsKey(comp))
                {
                    ZObjectManipulationInformation info = ElementsBeingManipulated[comp];
                    if (comp.PhysicsEnabled)
                    {
                        var currentPositionOnCanvas = new Point(Canvas.GetLeft(comp), Canvas.GetTop(comp));
                        double distance = Point.Subtract(info.OriginalPositionOnCanvas, currentPositionOnCanvas).Length;

                        if (distance < 20)
                        {
                            ElementsBeingManipulated.Remove(comp);
                            return;
                        }

                        ElementsBeingAccelerated[comp.InertiaProcessor] = info;
                        ElementsBeingManipulated.Remove(comp);

                        try
                        {
                            info.Left = Canvas.GetLeft(comp);
                            info.Top = Canvas.GetTop(comp);
                            comp.InertiaProcessor.InitialOriginX = e.OriginX;
                            comp.InertiaProcessor.InitialOriginY = e.OriginY;
                            comp.InertiaProcessor.ExpansionBehavior.DesiredExpansionX = e.Velocities.ExpansionVelocityX;
                            comp.InertiaProcessor.ExpansionBehavior.DesiredExpansionY = e.Velocities.ExpansionVelocityY;
                            comp.InertiaProcessor.RotationBehavior.InitialVelocity = e.Velocities.AngularVelocity;
                            comp.InertiaProcessor.Delta += InertiaProcessor_Manipulation2DDelta;
                            comp.InertiaProcessor.Completed += InertiaProcessor_Manipulation2DCompleted;
                            comp.InertiaProcessor.RotationBehavior.DesiredDeceleration = (float) 0.00005;
                            comp.InertiaProcessor.TranslationBehavior.DesiredDeceleration = (float) 0.001;
                            comp.Margin = new Thickness(50);
                            comp.BorderThickness = new Thickness(200, 200, Window.GetWindow(comp).ActualWidth - 200,
                                                                 Window.GetWindow(comp).ActualHeight - 200);
                            //comp.InertiaProcessor.ElasticMargin = new System.Windows.Thickness(50);
                            //comp.InertiaProcessor.Bounds = new System.Windows.Thickness(200, 200, Window.GetWindow(comp).ActualWidth - 200, Window.GetWindow(comp).ActualHeight - 200);
                        }
                        catch (NullReferenceException ne)
                        {
                            if (comp != null)
                            {
                                Console.WriteLine("Unknown object is null, see Inertia!");
                            }
                        }
                        try
                        {
                            Manipulation.StartInertia(comp);
                            //comp.InertiaProcessor.Begin();
                        }
                        catch (Exception ex)
                        {
                            var args = new ZObjectDragDropEventArgs();
                            args.RoutedEvent = ZObjectDragDropBehavior.DragFinishedEvent;
                            args.Source = info.ComponentBeingManipulated;
                            args.CurrentDragPoint = new Point(e.OriginX, e.OriginY);
                            info.ComponentBeingManipulated.RaiseEvent(args);
                        }
                    }
                }
            }
        }

        #endregion

        #region inertia

        private static void InertiaProcessor_Manipulation2DCompleted(object sender, Manipulation2DCompletedEventArgs e)
        {
            var processor = sender as InertiaProcessor2D;

            if (ElementsBeingAccelerated.ContainsKey(processor))
            {
                ZObjectManipulationInformation info = ElementsBeingAccelerated[processor];
                //Raise DragFinishedEvent and set IsDragging to false
                var args = new ZObjectDragDropEventArgs();
                args.RoutedEvent = ZObjectDragDropBehavior.DragFinishedEvent;
                args.Source = info.ComponentBeingManipulated;
                args.CurrentDragPoint = new Point(e.OriginX, e.OriginY);
                info.ComponentBeingManipulated.RaiseEvent(args);
                //ZObjectDragDropBehavior.SetIsDragging(info.ComponentBeingManipulated, false);

                ElementsBeingAccelerated.Remove(processor);
            }
        }

        private static void InertiaProcessor_Manipulation2DDelta(object sender, Manipulation2DDeltaEventArgs e)
        {
            var processor = sender as InertiaProcessor2D;

            if (ElementsBeingAccelerated.ContainsKey(processor))
            {
                ZObjectManipulationInformation info = ElementsBeingAccelerated[processor];

                ZComponent comp = info.ComponentBeingManipulated;

                if (ZObjectDragDropBehavior.GetIsDraggable(comp))
                {
                    //Raise DragDeltaEvent
                    var args = new ZObjectDragDropEventArgs();
                    args.CurrentDragPoint = new Point(e.OriginX, e.OriginY);
                    args.RoutedEvent = ZObjectDragDropBehavior.DragDeltaEvent;
                    args.Source = comp;
                    comp.RaiseEvent(args);

                    double scale = info.GetScaleFactor();

                    Canvas.SetLeft(comp, info.Left + e.Cumulative.TranslationX/scale);
                    Canvas.SetTop(comp, info.Top + e.Cumulative.TranslationY/scale);
                }

                if (ZObjectRotateBehavior.GetIsRotatable(comp))
                {
                    comp.Angle += e.Delta.Rotation;
                }
            }
        }

        #endregion
    }
}